module Show
  ( instr2c
  , proc2c
  , showProc
  ) where

import Prelude

import AST (Expr(..), Instr(..), Oper(..), Proc)
import Data.Foldable (foldMap)
import Data.FoldableWithIndex (foldMapWithIndex)
import Data.Monoid (power)

showInstr ∷ Instr -> Int -> String
showInstr (Move i) = line $ "MOVE " <> show i
showInstr (Update m) = line "UPDATE" <> indent (foldMapWithIndex showOper m)
  where
  showOper i (Add v) = line $ "ADD [" <> show i <> "] " <> show v
  showOper i (Set v) = line $ "SET [" <> show i <> "] " <> showExpr "" v
showInstr (Loop i) =
  line ("WHILE [" <> show i.idx <> "]")
    <> indent (showProc i.proc)
showInstr (In i) = line $ "INPUT [" <> show i <> "]"
showInstr (Out i) = line $ "OUTPUT [" <> show i <> "]"

showProc :: Proc -> Int -> String
showProc x d = foldMap (_ `showInstr` d) x

instr2c ∷ Instr -> Int -> String
instr2c (Move i) = line $ "p += " <> show i <> ";"
instr2c (Update m) = line "{" <> indent proc <> line "}"
  where
  proc = foldMapWithIndex decl m <> foldMapWithIndex perform m
  decl i (Add v) = declE i (EAdd (ECst v) (EDyn i))
  decl i (Set v) = declE i v
  declE i e = line $ "uint8_t " <> name i <> " = " <> showExpr "p" e <> ";"
  perform i _ = line $ "p[" <> show i <> "] = " <> name i <> ";"
  name i | i < 0 = "v_" <> show (-i)
  name i | otherwise = "v" <> show i
instr2c (Loop i) =
  line ("while (p[" <> show i.idx <> "]) {")
    <> indent (proc2c i.proc)
    <> line "}"
instr2c (In i) = line $ "p[" <> show i <> "] = getchar();"
instr2c (Out i) = line $ "putchar(p[" <> show i <> "]);"

proc2c :: Proc -> Int -> String
proc2c x d = foldMap (_ `instr2c` d) x

showExpr :: String -> Expr -> String
showExpr _ (ECst x) = show x
showExpr v (EDyn x) = v <> "[" <> show x <> "]"
showExpr v (ENeg x) = v <> "-" <> showExpr v x <> ""
showExpr v (EAdd a b) = "(" <> showExpr v a <> " + " <> showExpr v b <> ")"
showExpr v (ESub a b) = "(" <> showExpr v a <> " - " <> showExpr v b <> ")"
showExpr v (EMul a b) = "(" <> showExpr v a <> " * " <> showExpr v b <> ")"

line ∷ String -> Int -> String
line s d = power "  " d <> s <> "\n"

indent :: (Int -> String) -> Int -> String
indent f d = f (d + 1)
